home *** CD-ROM | disk | FTP | other *** search
/ MacTech 1 to 12 / MacTech-vol-1-12.toast / Source / MacTech® Magazine / Volume 10 - 1994 / 10.09 Sep 94 / Fez (MacHack Winner) / FezEdit ƒ / Fez.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-08-20  |  28.9 KB  |  844 lines  |  [TEXT/MMCC]

  1. /*
  2.     FezEdit: Frame-Evading Zoomrects Editor
  3.     
  4.      by Douglas McKenna
  5.          (c) Copyright 1994 All rights reserved.
  6.  
  7.          Mathemaesthetics, Inc.
  8.         PO Box 298 • Boulder • CO • 80306-0298 • USA
  9.         
  10.         Submitted as a hack and a half for MacHack, June 24, 1994
  11.         Winner (by popular acclaim) of Best Hack of the Contest
  12.         
  13.          Permission is granted to use this code in any non-commercial product.
  14.          Please contact the author for prior written approval for use in any
  15.          commercial or shareware product.
  16.          
  17.     This file contains the FEZ routines for implementing various forms
  18.     of Zooming Rects.
  19.  */
  20.  
  21.  
  22.  
  23. #include "Fez.h"
  24. #include "Utilities.h"
  25.  
  26. // Local globals
  27.  
  28. static GrafPort        deskPort;        // Temporary port to do all desktop drawing in
  29. static RgnHandle    deskClip;        // Its current clipping region
  30.  
  31. static RgnHandle    qClip[MAXQUEUE];
  32. static Rect            qRect[MAXQUEUE];
  33.  
  34. //    These macros perform the conversions on the frame within the
  35. //    window of a ZoomFrame struct using the utility routines
  36.  
  37. #define GlobalToLocalZF(zf) GlobalToLocalRect((zf)->win, &(zf)->frame)
  38. #define LocalToGlobalZF(zf) LocalToGlobalRect((zf)->win, &(zf)->frame)
  39.  
  40. #define CONTROLINSET    16
  41.  
  42. // Prototypes for local FEZ utility routines
  43.  
  44. int            InstallDesktop(void);
  45. void        ResetDesktop(void);
  46.  
  47. ///////////////////////////////////////////////////////////////////////////////
  48. /*
  49.  *    Create a new port the size of the current desktop, and initialize
  50.  *    its drawing environment for drawing and erasing gray zoomrects.
  51.  *    Deliver TRUE if all goes well, FALSE if memory or other problem.
  52.  *    You must match a successful call to InstallDeskTop with a call
  53.  *    to ResetDesktop.
  54.  */
  55.  
  56. static int InstallDesktop()
  57.     {
  58.         RgnHandle deskGray = LMGetGrayRgn();    // New low-mem accessor function/macro
  59.         
  60.           PushPort(NIL);                            // Save whatever the current port is
  61.           OpenPort(&deskPort);                    // Sets the current port to deskPort
  62.           if (MemError()) {
  63.               PopPort();
  64.               return(FALSE);
  65.               }
  66.           
  67.           CopyRgn(deskGray,deskPort.visRgn);        // Make it the same as GrayRgn
  68.           if (MemError()) {
  69.               ClosePort(&deskPort);                // Back out, no memory
  70.               PopPort();
  71.               return(FALSE);
  72.               }
  73.           deskPort.portRect = (*deskGray)->rgnBBox;
  74.           
  75.           // Get a copy of desktop clipping region
  76.           
  77.           deskClip = NewRgn();
  78.           if (deskClip) {
  79.               GetClip(deskClip);
  80.               PenPat(&qd.gray);                    // Draw in gray pattern
  81.               PenMode(notPatXor);                    // Using xor mode
  82.               }
  83.            else {
  84.               ClosePort(&deskPort);                // Back out, no memory
  85.               PopPort();
  86.               }
  87.  
  88.           return(deskClip != NIL);
  89.     }
  90.  
  91. /*
  92.  *    Restore the drawing environment in effect when InstallDesktop() was called
  93.  */
  94.  
  95. void ResetDesktop()
  96.     {
  97.         DisposeRgn(deskClip);
  98.         ClosePort(&deskPort);
  99.         PopPort();
  100.     }
  101.  
  102. /*
  103.  *    Instead of interpolating between the four corners of the two limiting
  104.  *    rects, we interpolate along a line between the two limiting rects’ centers,
  105.  *    and between their two sizes.  This code doesn't care about the
  106.  *    direction of the zoom, or whether either of the Rect's is empty.
  107.  */
  108.  
  109. void ZoomCenterRect(Rect *startRect, Rect *endRect, short thick, short speed, short qSize)
  110.     {
  111.         Point startCenter,endCenter,startSize,endSize;
  112.         short zoomSteps,x,y,w,h,q; long i;
  113.         
  114.         InstallDesktop();            // Draw outside of all windows
  115.         
  116.           // Get centers of the two limiting rectangles
  117.           
  118.         startCenter.h = (startRect->left + startRect->right) / 2;
  119.         startCenter.v = (startRect->top + startRect->bottom) / 2;
  120.         endCenter.h = (endRect->left + endRect->right) / 2;
  121.         endCenter.v = (endRect->top + endRect->bottom) / 2;
  122.         
  123.         // And the starting and ending half sizes
  124.         
  125.         startSize.h = (startRect->right - startRect->left) / 2;
  126.         startSize.v = (startRect->bottom - startRect->top) / 2;
  127.         endSize.h = (endRect->right - endRect->left) / 2;
  128.         endSize.v = (endRect->bottom - endRect->top) / 2;
  129.         
  130.         // Fill the rectangle queue with empty rectangles so nothing gets drawn
  131.         // initially as the queue fills up.  The queue makes for a better zoom
  132.         // effect, since there will be more graphic weight to the zooming, and
  133.         // the amount of time any particular rectangle is displayed is much longer.
  134.         
  135.         if (qSize < 1) qSize = 1;
  136.           SetRect(&qRect[0],0,0,0,0);
  137.           for (q=1; q<qSize; q++)
  138.               qRect[q] = qRect[0];
  139.         
  140.         PenSize(thick,thick);                    // Normal value should be (1,1)
  141.         
  142.         zoomSteps = 16;
  143.         for (i=0; i<=zoomSteps; i++) {            // loops zoomSteps+1 times
  144.         
  145.             // The following arithmetic is susceptable to overflow for short
  146.             // coordinates with large magnitudes (e.g. greater than (32K/zoomSteps),
  147.             // so we do our intermediate calculations in longs by making i a long.
  148.             // If zoomSteps is a power of 2, it is also a lot faster to replace
  149.             // the divide-by-zoomSteps with a right-shift by that power of 2,
  150.             // although it doesn't really matter since the loop has a 1-tick governor.
  151.             
  152.             // Find the i'th intermediate position along line between centers
  153.             x = ((zoomSteps-i)*startCenter.h + i*endCenter.h) / zoomSteps;
  154.             y = ((zoomSteps-i)*startCenter.v + i*endCenter.v) / zoomSteps;
  155.             // And i'th intermediate sizes (interpolated linearly)
  156.             w = ((zoomSteps-i)*startSize.h + i*endSize.h) / zoomSteps;
  157.             h = ((zoomSteps-i)*startSize.v + i*endSize.v) / zoomSteps;
  158.  
  159.             // Build the next interpolated rectangle
  160.             SetRect(&qRect[qSize-1],x-w,y-h,x+w,y+h);
  161.             // Draw the newest zooming rectangle in the queue
  162.             FrameRect(&qRect[qSize-1]);
  163.             // Erase (assuming xor mode) the i-3'rd previously drawn rectangle
  164.             FrameRect(&qRect[0]);
  165.             // Shift rectangle queue up by 1, leaving r4 ready to be redefined
  166.             for (q=1; q<qSize; q++)
  167.                 qRect[q-1] = qRect[q];
  168.  
  169.             // Use governor so processor speed doesn't affect zoom
  170.             Wait(speed);
  171.             if (speed > 5)
  172.                 {
  173.                 EventRecord event;
  174.                 while (!GetNextEvent(keyDownMask,&event))
  175.                     SystemTask();
  176.                 // Allow system to handle a screen dump FKEY
  177.                 }
  178.             }
  179.         
  180.         // Erase last three zoomrects to empty the queue of drawn zoomrects
  181.  
  182.         for (q=1; q<qSize; q++) {
  183.             FrameRect(&qRect[q-1]);
  184.             Wait(speed);
  185.             }
  186.  
  187.         ResetDesktop();            // Restore normal drawing environment
  188.     }
  189.  
  190. /*
  191.  *    This function precomputes an array of ZoomFrames for doing various
  192.  *    types of zooms on the desktop between theWidget and theWindow frames.
  193.  *    The delivered array will have at least 2 entries, with the first and
  194.  *    last entries the same as the arguments.  When opening is non-zero (TRUE),
  195.  *    then the zoom travels from theWidget to theWindow; otherwise, the zoom
  196.  *    travels from theWindow to theWidget.  The delivered array must be
  197.  *    passed to FrameEvadingZoom() to perform the actual zoom drawing later,
  198.  *    using the zoom style provided here.
  199.  *
  200.  *    The caller must fill in the initial .win, .frame, and .thickness fields
  201.  *    of each ZoomFrame argument before passing them into this routine.
  202.  *    The frame rectangle for theWidget should be in LOCAL coordinates with
  203.  *    respect to theWidget->win.  The frame rectangle for theWindow should
  204.  *    be in GLOBAL screen coordinates.  In either case, theWindow->win
  205.  *    should be in its final or current position in the Window List.
  206.  *    If opening, you will want to show the window after doing the zoom;
  207.  *    if closing, you will want to hide the window before doing the zoom.
  208.  *
  209.  *    By calling this first, before drawing the zoom, we avoid having
  210.  *    to worry about changes to the window order messing us up when the
  211.  *    caller eventually calls HideWindow (which changes the window order)
  212.  *    prior to doing the actual zoom using the array.
  213.  *
  214.  *    The ZoomArray is allocated as a relocatable block on the heap and
  215.  *    the caller must dispose of it with DisposeZoom.
  216.  *
  217.  *    zoomStyle is 0 for no zoom
  218.  *                 1 for standard linear zoom
  219.  *                 2 for one-window dive
  220.  *                 3 for full frame-evading
  221.  *
  222.  *    Delivers NIL if not enough memory or other problem.
  223.  */
  224.  
  225. ZoomFrameHandle NewZoom(ZoomFrame *theWidget, ZoomFrame *theWindow, int opening, int zoomStyle)
  226.     {
  227.         RgnHandle clipRgn,tmpRgn; ZoomFrame *zf;
  228.         ZoomFrameHandle zoomArray = NIL;
  229.         short numFrames,w,h,width,height,x,y,k,i;
  230.         WindowPeek wp; Rect ans,*bounds,*bbox;
  231.         int okay = FALSE;
  232.         
  233.         // Reality checks
  234.         if (theWidget==NIL || theWindow==NIL ||
  235.             theWidget->win==NIL || theWindow->win==NIL ||
  236.             theWidget->win==theWindow->win)
  237.             return(NIL);
  238.         
  239.         // Install parameters in theWindow's private fields to pass to FEZ
  240.         theWindow->opening = opening;
  241.         theWindow->zoomStyle = zoomStyle;
  242.         
  243.         // Start with entire visible desktop as clipping region for theWindow
  244.         clipRgn = NewRgn();
  245.         if (clipRgn == NIL) goto cleanup;    
  246.         CopyRgn(LMGetGrayRgn(),clipRgn);
  247.         if (MemError()) goto cleanup;
  248.         
  249.         // Subtract out all visible windows that are in front of theWindow.
  250.         // These are typically floating palettes (or text input windows or balloons).
  251.         
  252.         wp = (WindowPeek)LMGetWindowList();                        // For each window from front,
  253.         while (wp!=(WindowPeek)theWindow->win && wp!=NIL) {        //  up to theWindow's,
  254.             if (wp->visible) {                                    //  if it's visible,
  255.                 DiffRgn(clipRgn,wp->strucRgn,clipRgn);            //  cut out its structure region
  256.                 if (MemError()) goto cleanup;
  257.                 }
  258.             wp = wp->nextWindow;                                // On to next window in list
  259.             }
  260.         if (wp == NIL) goto cleanup;                // Reality check: should never happen
  261.  
  262.         // clipRgn now contains all pixels on desktop except those that belong
  263.         // to windows in front of theWindow's.
  264.         
  265.         // Get maximum number of windows for which we might have to create array entries
  266.         
  267.         if (zoomStyle == 3) {
  268.             numFrames = opening ? 2 : 1;            // theWindow is invisible when opening
  269.             wp = (WindowPeek)theWindow->win;
  270.             while (wp!=(WindowPeek)theWidget->win && wp!=NIL) {
  271.                 if (wp->visible) numFrames++;
  272.                 wp = wp->nextWindow;
  273.                 }
  274.             if (wp == NIL) {
  275.                 // Uh, ohh...theWidget->win is in front of theWindow->win or
  276.                 // not in the window list.
  277.                 // Not sure what the right thing to do is in this very unlikely
  278.                 // case, so we deliver NIL to do nothing, or do a standard zoom.
  279.                 goto cleanup;
  280.                 }
  281.             if (numFrames < 2) {
  282.                 // Uh, ohh... theWidget->win was found, but was invisible
  283.                 // Skip town again
  284.                 goto cleanup;
  285.                 }
  286.             }
  287.          else
  288.             numFrames = 2;
  289.         
  290.         // Determine whether the widget is wholly visible in its window or not.
  291.         // theWidget's frame is expected to already be in local coordinates.
  292.         
  293.         theWidget->isHidden = TRUE;
  294.         if (SectRect(&theWidget->win->portRect,&theWidget->frame,&ans))
  295.             if (EqualRect(&ans,&theWidget->frame))
  296.                 theWidget->isHidden = FALSE;
  297.         
  298.         // Create maximum array storage for entries, initialized to 0.
  299.         zoomArray = (ZoomFrameHandle)NewHandleClear(numFrames * sizeof(ZoomFrame));
  300.         if (zoomArray == NIL) goto cleanup;
  301.         
  302.         // Place theWindow at start of array, regardless of zoom direction
  303.         zf = *zoomArray;
  304.         *zf = *theWindow;
  305.         zf->clip = clipRgn; clipRgn = NIL;        // Pass off clipRgn to first entry
  306.         
  307.         // Traverse down the window list until we hit the widget's window
  308.         numFrames = 1;
  309.         wp = (WindowPeek)theWindow->win;
  310.         if (wp) wp = wp->nextWindow;                // Skip first, we just did it above
  311.         while (wp != (WindowPeek)theWidget->win) {
  312.             if (wp->visible && zoomStyle==3) {
  313.             
  314.                 // This is a good place to do any checks to cull windows that
  315.                 // pose no threat to the zoom path.  This is a pretty hard
  316.                 // problem, but at the very least, we can ignore windows
  317.                 // that do not cover the widget's window's content region,
  318.                 // to which we will eventually be clipping.
  319.                 bounds = &(*wp->strucRgn)->rgnBBox;
  320.                 bbox = &(*((WindowPeek)theWidget->win)->contRgn)->rgnBBox;
  321.                 if (SectRect(bbox,bounds,&ans)) {
  322.                 
  323.                     // Intersection: need another knot in spline and intermediate ZF
  324.                     // Initialize the next intermediate ZoomRect's clipping region
  325.                     // as whatever we've got before, minus its window's structure.
  326.                     
  327.                     tmpRgn = NewRgn();                        // Use tmp before dereference
  328.                     (*zoomArray)[numFrames].clip = tmpRgn;
  329.                     if (tmpRgn) {
  330.                         DiffRgn((*zoomArray)[numFrames-1].clip,wp->strucRgn,tmpRgn);
  331.                         if (MemError()) goto cleanup;
  332.                         }
  333.                      else
  334.                         goto cleanup;
  335.                     
  336.                     // Tell it which window it is evading, and keep same line width
  337.                     
  338.                     zf = (*zoomArray) + numFrames;
  339.                     zf->win = (WindowPtr)wp;
  340.                     zf->thickness = theWindow->thickness;
  341.                     
  342.                     // We will add information to the array after it has been
  343.                     // created, since in theory you may need to do some kind of
  344.                     // global search on the entire set of frames to find the best
  345.                     // spline path.  In the meantime, we record the window's
  346.                     // structure region's bounds to be used below in the next stage.
  347.                     
  348.                     zf->frame = (*wp->strucRgn)->rgnBBox;    // Global coordinates
  349.  
  350.                     numFrames++;
  351.                     }
  352.                 }
  353.             wp = wp->nextWindow;
  354.             }
  355.         
  356.         // Set the last zoom to the ending ZoomFrame for widget, and cut the array down
  357.         // to its final size, since we allocated a maximum number of entries above
  358.         // but may have culled some windows above.
  359.         
  360.         (*zoomArray)[numFrames] = *theWidget;
  361.         tmpRgn = NewRgn();                        // Use temporary so we don't have to
  362.         (*zoomArray)[numFrames].clip = tmpRgn;    // lock zoomArray down during NewRgn.
  363.         
  364.         if (tmpRgn)
  365.             CopyRgn((*zoomArray)[numFrames-1].clip,tmpRgn);
  366.          else
  367.             goto cleanup;
  368.         
  369.         // Cut the thing down to size
  370.         numFrames++;
  371.         SetHandleSize((Handle)zoomArray,numFrames*sizeof(ZoomFrame));
  372.         HLock((Handle)zoomArray);
  373.         // Convert theWidget's frame to global coords like all the other entries will be
  374.         zf = (*zoomArray) + numFrames - 1;
  375.         LocalToGlobalZF(zf);
  376.         
  377.         // Got our zoom array, now all we have left to do is compute the
  378.         // frame positions next to the windows, and the knots and Bezier
  379.         // control points within them.  In order to evade each window frame,
  380.         // the zoom has to find its way around the window, either above or
  381.         // below or to the right or to the left (or we could use octants
  382.         // for complete generality).
  383.         
  384.         zf = *zoomArray;
  385.         for (i=0; i<numFrames; i++,zf++) {            // For each ZoomFrame...
  386.         
  387.             // For all the internal frames, change the frame position.
  388.             // The end frames are already in their final positions.
  389.             if (i>0 && i<(numFrames-1)) {
  390.             
  391.                 // Get width and height of window structure
  392.                 width = w = (zf->frame.right - zf->frame.left);
  393.                 height = h = (zf->frame.bottom - zf->frame.top);
  394.                 
  395.                 // For maximum amusement, we set the midway zooms to be
  396.                 // on various sides of the window frames, half size.
  397.                 // Basically, rotate our zoom around each successive window
  398.                 // This maximizes slinkyness and is very silly, but good for
  399.                 // demo purposes.  This is the spot in the routine where it
  400.                 // would be appropriate to analyze the window pattern as a
  401.                 // whole to try to optimize a simple path that evades the
  402.                 // group as a whole to get to the destination frame.  For
  403.                 // a small number of windows, you could even to a complete
  404.                 // backtrack search to find the shortest path, but I'll
  405.                 // leave that for another day.
  406.                 
  407.                 // Turn w and h into an offset to one side of window
  408.                 
  409.                 k = i & 3;                    // Cycle every four frames
  410.                 switch(k) {
  411.                     case 0: w = 0; h = -(h+16); break;    // 16 pixels above window
  412.                     case 1: w =  (w+16); h = 0; break;    // 16 pixels to right of window
  413.                     case 2: w = 0; h =  (h+16); break;    // 16 pixels below window
  414.                     case 3: w = -(w+16); h = 0; break;    // 16 pixels to left of window
  415.                     }
  416.                 
  417.                 OffsetRect(&zf->frame,w,h);        // Move it 16 pixels outside window
  418.                 // Make the thing smaller than entire window bounds
  419.                 InsetRect(&zf->frame,width/4,height/4);
  420.                 }
  421.             
  422.             // Set knot to the center of its zoom rect bounds for all frames
  423.             zf->knot.x = (zf->frame.left + zf->frame.right) / 2;
  424.             zf->knot.y = (zf->frame.top + zf->frame.bottom) / 2;
  425.             }
  426.  
  427.         // Finally, traverse the array, using line segments between knots to
  428.         // choose spline control points that form a "smooth" path.
  429.         // If the two control points on either side of a Bezier spline knot
  430.         // are colinear, then the spline is continuous through the knot.
  431.         // This means the zoom won't too suddenly change direction as at passes
  432.         // each individual ZoomFrame entry in the zoomArray.  The hard part
  433.         // is figuring out how to set the line that the two control points
  434.         // have to be on so that the path isn't too crazy.  Note that if we
  435.         // have only 2 entries in the array, we don't have any intermediate
  436.         // knots to worry about.
  437.         //
  438.         // Naturally, this code would need to be sped up for older 68K macs,
  439.         // especially since the first time it's called it'll have to load
  440.         // the SANE package, but it seems to work reasonably fast on my
  441.         // PowerBook 180c (68030).
  442.         
  443.         zf = *zoomArray;                        // It's still locked
  444.         zf->c0 = zf->knot;
  445.         
  446.         for (zf++,k=1; k<(numFrames-1); k++,zf++) {
  447.         
  448.             double theta,dot,cross,sn,cs;
  449.             long dx0,dy0,dx1,dy1;
  450.             
  451.             dx0 = zf->knot.x - (zf-1)->knot.x;    // Vector from last knot to this one
  452.             dy0 = zf->knot.y - (zf-1)->knot.y;
  453.             dx1 = (zf+1)->knot.x - zf->knot.x;    // Vector from this knot to next one
  454.             dy1 = (zf+1)->knot.y - zf->knot.y;
  455.             
  456.             // Get the angle between the two vectors, (dx0,dy0) --> (dx1,dy1)
  457.             dot   = dx0*dx1 + dy0+dy1;            //   Dot product = len0 * len1 * cos(theta)
  458.             cross = dx0*dy1 - dx1*dy0;            // Cross product = len0 * len1 * sin(theta)
  459.             theta = atan2(cross,dot);
  460.             
  461.             // Get the sin and cosine of half the angle
  462.             theta = theta / 2.0;
  463.             sn = sin(theta);
  464.             cs = cos(theta);
  465.             
  466.             // Rotate our initial vector by half the angle, and shrink it to a third
  467.             // its size (purely a heuristic: the larger this vector is, the more
  468.             // boisterous (wider) the Bezier turn will be.  The result will be the
  469.             // vector between control points (through the knot) for this frame.
  470.             x = (cs*dx0 - sn*dy0) / 3.0;
  471.             y = sn*dx0 + cs*dy0 / 3.0;
  472.             
  473.             // Set the two colinear control points for the next knot
  474.             (zf-1)->c1.x = zf->knot.x - x;
  475.             (zf-1)->c1.y = zf->knot.y - y;
  476.             zf->c0.x = zf->knot.x + x;
  477.             zf->c0.y = zf->knot.y + y;
  478.             }
  479.         
  480.         zf->c1 = zf->knot;
  481.         
  482.         HUnlock((Handle)zoomArray);
  483.         okay = TRUE;                    // Tell cleanup not to throw zoomArray away
  484.  
  485. cleanup:
  486.         if (clipRgn) DisposeRgn(clipRgn);
  487.         if (!okay) {
  488.             DisposeZoom(zoomArray);
  489.             zoomArray = NIL;
  490.             }
  491.         
  492.         return(zoomArray);
  493.     }
  494.  
  495. /*
  496.  *    Throw away the given zoomArray (if it's non-NIL) and throw away all
  497.  *    internal clipping regions (and whatever else) as well.
  498.  */
  499.  
  500. void DisposeZoom(ZoomFrameHandle zoomArray)
  501.     {
  502.         if (zoomArray) {
  503.             long numZooms = GetHandleSize((Handle)zoomArray) / sizeof(ZoomFrame);
  504.             while (numZooms-- > 0) {
  505.                 RgnHandle clip = (*zoomArray)[numZooms].clip;
  506.                 if (clip) DisposeRgn(clip);
  507.                 }
  508.             }
  509.     }
  510.  
  511. /*
  512.  *    Given an array of ZoomFrames, as previously created by NewZoom, animate
  513.  *    the zoom on the desktop.
  514.  *    The array is always in canonical order from
  515.  *    theWindow to theWidget, so if we are opening (as found in theWindow)
  516.  *    we have to traverse the array backwards.
  517.  */
  518.  
  519. void FrameEvadingZoom(ZoomFrameHandle zoomArray, short speed, short qSize)
  520.     {
  521.         Point midCenter,startSize,midSize,endSize,pt;
  522.         Rect midway,clip,content;
  523.         short zoomSteps,zoomSteps2,x,y,w,h,n,numPairs,numFrames,zfInc,q;
  524.         long i,j; Point2D p0,c1,c2,p3,path[MAXPATH+1]; int useDive;
  525.         ZoomFrame *start,*end,*theWindow,*theWidget;
  526.         RgnHandle tmpClip;
  527.  
  528.         // Reality check
  529.         if (zoomArray == NIL)
  530.             return;
  531.         
  532.         // Draw outside of all windows; deskPort becomes current port
  533.         if (!InstallDesktop())
  534.             return;
  535.         // From now on, always return via cleanup so port gets restored
  536.         
  537.         // Fill the clipped rectangle queue with empty rectangles so nothing gets drawn
  538.         // as the queue fills up.  The queue makes for a better zoom effect, since there
  539.         // will be more graphic weight to the zooming, and the amount of time any
  540.         // particular zoomrect is displayed is much longer.
  541.  
  542.         if (qSize < 1) qSize = 1;
  543.           SetRect(&qRect[0],0,0,0,0);
  544.           for (q=1; q<qSize; q++)
  545.               qRect[q] = qRect[0];
  546.         // Queued clipping regions 1 through 4 are allocated empty
  547.         for (q=0; q<qSize; q++)
  548.             qClip[q] = NewRgn();
  549.         if (qClip[qSize-1] == NIL) goto cleanup;
  550.  
  551.         // Now do a zoom between each pair of adjacent ZoomRects in the
  552.         // array, in which there is always at least one pair (2 entries).
  553.         
  554.         HLock((Handle)zoomArray);
  555.         numFrames = GetHandleSize((Handle)zoomArray) / sizeof(ZoomFrame);
  556.         numPairs = numFrames - 1;
  557.         
  558.         theWindow = (*zoomArray) + 0;
  559.         theWidget = (*zoomArray) + (numFrames-1);
  560.  
  561. #ifdef DEBUGONLY
  562.         if (speed > 5)
  563.         {
  564.             start = theWindow;
  565.             end = start + 1;
  566.             for (n=0; n<numPairs; n++,start++,end++) {
  567.                 ComputeBezierPath(&start->knot,&start->c0,&start->c1,&end->knot,path,MAXPATH);
  568.                 DrawBezierPath(path,MAXPATH);
  569.                 }
  570.             Wait(3*speed);
  571.             start = theWindow;
  572.             end = start + 1;
  573.             for (n=0; n<numPairs; n++,start++,end++) {
  574.                 ComputeBezierPath(&start->knot,&start->c0,&start->c1,&end->knot,path,MAXPATH);
  575.                 DrawBezierPath(path,MAXPATH);
  576.                 }
  577.         }
  578. #endif
  579.  
  580.         // Each iteration slides start/end up or down by one in the array
  581.         
  582.         if (theWindow->opening) {
  583.             start = theWidget;
  584.             zfInc = -1;                        // Down
  585.             }
  586.          else {
  587.             start = theWindow;
  588.             zfInc = 1;                        // Up
  589.             }
  590.         end = start + zfInc;
  591.         
  592.         for (n=0; n<numPairs; n++,start+=zfInc,end+=zfInc) {
  593.         
  594.             // Get the starting and ending half sizes
  595.             
  596.             startSize.h = (start->frame.right - start->frame.left) / 2;
  597.             startSize.v = (start->frame.bottom - start->frame.top) / 2;
  598.             endSize.h = (end->frame.right - end->frame.left) / 2;
  599.             endSize.v = (end->frame.bottom - end->frame.top) / 2;
  600.             
  601.             // Knots are the same regardless of opening or closing
  602.             p0 = start->knot;
  603.             p3 = end->knot;
  604.             
  605.             if ((theWindow->opening && start==theWidget) ||
  606.                 (!theWindow->opening && end==theWidget)) {
  607.             
  608.                 // Get content area minus the scroll bar and grow icon areas
  609.                 // This assumes window has both right and bottom scroll bars
  610.                 // To be truly general, we should be using a content region.
  611.                 content = theWidget->win->portRect;
  612.                 content.right -= SCROLLBARWIDTH;
  613.                 content.bottom -= SCROLLBARWIDTH;
  614.                 
  615.                 // Find a rectangle within the window to serve as an intermediate
  616.                 // destination for the zoom that is completely contained in the
  617.                 // visible area of the destination zoom window.  When the zoom
  618.                 // reaches midway, we'll change the clipping region.
  619.                 midway.left = content.left;
  620.                 midway.top = content.top;
  621.                 midway.right = (content.right + (theWidget->frame.right-theWidget->frame.left)) / 2;
  622.                 midway.bottom = (content.bottom + (theWidget->frame.bottom-theWidget->frame.top)) / 2;
  623.                 CenterRect(&midway,&content,&midway);
  624.                 
  625.                 // If midway is larger than window, just use inset window content bounds
  626.                 if (midway.left<content.left || midway.top<content.top ||
  627.                         midway.right>content.right || midway.bottom>content.bottom) {
  628.                     midway = content;
  629.                     InsetRect(&midway,8,8);
  630.                     }
  631.                  else {
  632.                     /* Don't let midway's dimensions get larger than source/destination window's */
  633.                     short margin = ((midway.right-midway.left) - (theWindow->frame.right-theWindow->frame.left)) / 2;
  634.                     if (margin > 0) {
  635.                         midway.left += margin; midway.right -= margin;
  636.                         }
  637.                     margin = ((midway.bottom-midway.top) - (theWindow->frame.bottom-theWindow->frame.top)) / 2;
  638.                     if (margin > 0) {
  639.                         midway.top += margin; midway.bottom -= margin;
  640.                         }
  641.                     }
  642.                 
  643.                 // Convert to global coords and get center and half-sizes
  644.                 LocalToGlobalRect(theWidget->win,&midway);
  645.                 midSize.h = (midway.right - midway.left) / 2;
  646.                 midSize.v = (midway.bottom - midway.top) / 2;
  647.                 midCenter.h = (midway.right + midway.left) / 2;
  648.                 midCenter.v = (midway.bottom + midway.top) / 2;
  649.                 
  650.                 // Get window content as clipping rectangle in global coords
  651.                 clip = content;
  652.                 LocalToGlobalRect(theWidget->win,&clip);
  653.                 
  654.                 // But have to take intersection of it with final window clipping region
  655.                 
  656.                 tmpClip = NewRgn();
  657.                 if (tmpClip) {
  658.                     RectRgn(tmpClip,&clip);
  659.                     SectRgn(theWidget->clip,tmpClip,theWidget->clip);
  660.                     DisposeRgn(tmpClip);
  661.                     }
  662.                 
  663.                 // Set control (tension) points to other side of window in global coordinates
  664.                 pt.h = (content.left + content.right) / 2;
  665.                 pt.v = (content.top + content.bottom) / 2;
  666.  
  667.                 if (theWidget->frame.bottom > midway.bottom) pt.v = content.top + CONTROLINSET;
  668.                 if (theWidget->frame.top < midway.top) pt.v = content.bottom - CONTROLINSET;
  669.                 if (theWidget->frame.right > midway.right) pt.h = content.left + CONTROLINSET;
  670.                 if (theWidget->frame.left < midway.left) pt.h = content.right - CONTROLINSET;
  671.  
  672.                 PushPort(theWidget->win);
  673.                 LocalToGlobal(&pt);
  674.                 PopPort();
  675.                 
  676.                 c1.x = c2.x = pt.h;
  677.                 c1.y = c2.y = pt.v;
  678.                 useDive = TRUE;
  679.                 }
  680.              else {
  681.                 // Not final widget window: still evading window frames
  682.                 useDive = FALSE;
  683.                 // Since the control points for the segment between each pair are
  684.                 // stored in only one ZoomFrame, we have to use the fact that
  685.                 // we're opening or not to get the right ones.
  686.                 if (theWindow->opening) {
  687.                     c1 = end->c1;                // Traversing array backwards
  688.                     c2 = end->c0;
  689.                     }
  690.                  else {
  691.                     c1 = start->c0;                // Traversing it forwards
  692.                     c2 = start->c1;
  693.                     }
  694.                 }
  695.             
  696.             // Precompute the spline's path, including both endpoints
  697.             
  698.             ComputeBezierPath(&p0,&c1,&c2,&p3,path,MAXPATH);
  699.             
  700.             // DrawBezierPath(path,MAXPATH);
  701.             // Wait(speed*2);
  702.             // DrawBezierPath(path,MAXPATH);
  703.             
  704.             // You could interpolate among bounding pen sizes if you wanted to give
  705.             // more of a depth effect, but this would probably only be worth doing
  706.             // in a higher resolution world.
  707.             PenSize(start->thickness,start->thickness);
  708.             
  709.             zoomSteps = MAXPATH;
  710.             zoomSteps2 = zoomSteps/2;                // Halfway point
  711.             
  712.             for (i=0; i<=zoomSteps; i++) {            // loops zoomSteps+1 times
  713.             
  714.                 x = path[i].x;
  715.                 y = path[i].y;
  716.     
  717.                 if (useDive) {
  718.                     // If opening, start is theWidget and end is before it in the array.
  719.                     // If closing, end is theWidget and start is after it in the array.
  720.                     // In either case, we have to change the clipping region half way
  721.                     // through the zoom, when it reaches midway.
  722.                     if (i <= zoomSteps2) {
  723.                         // First half of zoom
  724.                         w = ((zoomSteps2-i)*startSize.h + i*midSize.h) / zoomSteps2;
  725.                         h = ((zoomSteps2-i)*startSize.v + i*midSize.v) / zoomSteps2;
  726.                         SetClip(start->clip);
  727.                         }
  728.                      else {
  729.                          // Second half of zoom: interpolate from midway to end within window
  730.                          j = i - zoomSteps2;
  731.                         w = ((zoomSteps2-j)*midSize.h + j*endSize.h) / zoomSteps2;
  732.                         h = ((zoomSteps2-j)*midSize.v + j*endSize.v) / zoomSteps2;
  733.                         SetClip(end->clip);
  734.                         }
  735.                     }
  736.                  else {
  737.                     // Get i'th intermediate size (interpolated linearly) for whole zoom
  738.                     w = ((zoomSteps-i)*startSize.h + i*endSize.h) / zoomSteps;
  739.                     h = ((zoomSteps-i)*startSize.v + i*endSize.v) / zoomSteps;
  740.                     // Set clipping to exclude all higher windows
  741.                     if (theWindow->opening) SetClip(end->clip);
  742.                      else                    SetClip(start->clip);
  743.                     }
  744.  
  745.                 GetClip(qClip[qSize-1]);
  746.                 SetRect(&qRect[qSize-1],x-w,y-h,x+w,y+h);
  747.                 FrameRect(&qRect[qSize-1]);
  748.  
  749.                 // Erase (assuming xor mode) the i-(qSize-1)'th previously drawn rectangle
  750.                 SetClip(qClip[0]);
  751.                 FrameRect(&qRect[0]);
  752.                 
  753.                 // Shift clipped rectangle queue up by 1, leaving r4 ready to be redefined
  754.                 tmpClip = qClip[0];
  755.                 for (q=1; q<qSize; q++) {
  756.                     qRect[q-1] = qRect[q];
  757.                     qClip[q-1] = qClip[q];
  758.                     }
  759.                 qClip[qSize-1] = tmpClip;
  760.  
  761.                 // Use governor so processor speed doesn't affect zoom
  762.                 Wait(speed);
  763.                 }
  764.             }
  765.         
  766.         HUnlock((Handle)zoomArray);
  767.         
  768.         // Erase last qSize-1 zoomrects to empty the queue of drawn zoomrects
  769.  
  770.         for (q=1; q<qSize; q++) {
  771.             SetClip(qClip[q-1]);
  772.             FrameRect(&qRect[q-1]);
  773.             Wait(speed);
  774.             }
  775.  
  776. cleanup:
  777.         for (q=0; q<qSize; q++) {
  778.             if (qClip[q]) DisposeRgn(qClip[q]);
  779.             qClip[q] = NIL;
  780.             }
  781.  
  782.         ResetDesktop();            // Restore normal drawing environment
  783.     }
  784.  
  785. /*
  786.  *    Precompute the path of a Bezier segment in 2D coordinates
  787.  *    whose starting and ending points are p0 and p3, and whose control points are
  788.  *    c1 and c2.  The path should be stored in the array "path", which is expected
  789.  *    to be able to hold (numPoints+1) elements, from 0 to numPoints, inclusive.
  790.  *    numPoints should be a power of 2 between 2 and 32, inclusive.  This routine
  791.  *    can be easily generalized to 3D (or higher) coordinates, and is optimized
  792.  *    for fast computation in (long) integers.  Since there are no divides, the
  793.  *    routine does the right thing regardless of whether p0, c1, c2, or p3 are
  794.  *    all different or coincident or whatever.
  795.  */
  796.  
  797. void ComputeBezierPath(Point2D *p0, Point2D *c1, Point2D *c2, Point2D *p3,
  798.                                                 Point2D *path, short numPoints)
  799.     {        
  800.         long i,ax,ay,bx,by,cx,cy,curx,cury;
  801.         short s1,s2,s3;
  802.  
  803.         curx = p0->x; cury = p0->y;
  804.  
  805.         //    Compute the integer Bezier coefficients, a, b, and c
  806.  
  807.         cx = (c1->x - curx); cx += cx << 1;            // times 3
  808.         cy = (c1->y - cury); cy += cy << 1;
  809.         
  810.         bx = (c2->x - c1->x); bx += (bx << 1) - cx;    // times 3 - c
  811.         by = (c2->y - c1->y); by += (by << 1) - cy;
  812.         
  813.         ax = (p3->x - curx) - cx - bx;
  814.         ay = (p3->y - cury) - cy - by;
  815.  
  816.         if         (numPoints == 32) s1 = 5;
  817.          else if (numPoints == 16) s1 = 4;
  818.          else if (numPoints ==  8) s1 = 3;
  819.          else if (numPoints ==  4) s1 = 2;
  820.          else                       s1 = 1;
  821.         
  822.         s2 = s1+s1; s3 = s2+s1;        // s3 is 15 bits worth of scaling
  823.         
  824.         bx   <<= s1;   by <<= s1;    // Scale operands up for later, according
  825.         cx   <<= s2;   cy <<= s2;    // to the degree in i in loop below
  826.         curx <<= s3; cury <<= s3;
  827.  
  828.         for (i=0; i<=numPoints; i++) {
  829.             path[i].x = (i * (i * (i * ax + bx) + cx) + curx) >> s3;
  830.             path[i].y = (i * (i * (i * ay + by) + cy) + cury) >> s3;
  831.             }
  832.   }
  833.  
  834. static void DrawBezierPath(Point2D *path, short numPoints)
  835.     {
  836.         Point2D *p = path;
  837.         
  838.         MoveTo(p->x,p->y);
  839.         while (numPoints-- > 0) {
  840.             p++;
  841.             LineTo(p->x,p->y);
  842.             }
  843.     }
  844.